package org.nox.helloseries.server.services.url.impl;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.SocketTimeoutException;
import java.net.URL;
import java.util.Map.Entry;

import org.apache.commons.io.FileUtils;
import org.apache.http.HttpEntity;
import org.apache.http.HttpHeaders;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.BasicCookieStore;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.cookie.BasicClientCookie;
import org.nox.helloseries.domain.NomTracker;
import org.nox.helloseries.domain.Serie;
import org.nox.helloseries.domain.SerieFileInformation;
import org.nox.helloseries.domain.Tracker;
import org.nox.helloseries.factory.HelloSeriesServiceFactory;
import org.nox.helloseries.server.exceptions.CookieException;
import org.nox.helloseries.server.services.browser.CookieSearchService;
import org.nox.helloseries.server.services.configuration.TrackerConfigurationService;
import org.nox.helloseries.server.services.html.HTMLSearchService;
import org.nox.helloseries.server.services.url.UrlService;
import org.nox.helloseries.util.CoreUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class DefaultUrlService implements UrlService
{
    private static Logger LOG = LoggerFactory.getLogger( DefaultUrlService.class );

    private static final int TIMEOUT_MS = 10000;

    private static final String TORRENT_CONTENT_TYPE = "application/x-bittorrent";

    // Services
    private TrackerConfigurationService trackerConfigurationService = HelloSeriesServiceFactory.createOrGetService( TrackerConfigurationService.class );

    private CookieSearchService cookieSearchService = HelloSeriesServiceFactory.createOrGetService( CookieSearchService.class );

    private HTMLSearchService htmlSearchService = HelloSeriesServiceFactory.createOrGetService( HTMLSearchService.class );

    @Override
    public File downloadSerieToTemporaryFolder( Serie serie ) throws IOException, CookieException
    {
        File file = null;

        if ( serie.getSerieFileInformation().getNomTrackerSource().isPrivate() )
        {
            LOG.info( "La série a été trouvé sur un tracker privé. Les infos de connexion pour l'injection des cookies vont être mises à jour" );
            SerieFileInformation serieFileInformation = serie.getSerieFileInformation();
            Tracker tracker = updateTrackerInformation( serieFileInformation.getNomTrackerSource() );

            return getTorrentFile( serie, serieFileInformation, tracker );

        }
        // Cas tracker classique sans authentification via cookie
        else
        {
            // Création du fichier
            file = new File( CoreUtils.getNouveauNomFichierFormate( serie ) );
            try
            {
                LOG.info( "Upload du fichier torrent en cours vers : {}", file.getAbsolutePath() );
                // ouverture de l'url
                URL url = new URL( serie.getSerieFileInformation().getUrl() );

                // Copie du contenu dans le fichier
                FileUtils.copyURLToFile( url, file );
            }
            catch ( IOException e )
            {
                throw e;
            }
        }
        return file;
    }

    private File getTorrentFile( Serie serie, SerieFileInformation serieFileInformation, Tracker tracker ) throws IOException, FileNotFoundException, CookieException
    {
        // ouverture de la connexion
        HttpEntity httpEntity = getHttpEntityFromUrl( serieFileInformation.getUrl(), tracker );

        // Vérification que le fichier retourné est bien un fichier torrent
        String contentType = httpEntity.getContentType().getValue();

        if ( contentType.contains( TORRENT_CONTENT_TYPE ) )
        {
            LOG.info( "Ce fichier est identifié comme un fichier torrent" );
            InputStream in = httpEntity.getContent();

            LOG.debug( "Transfert du flux vers {}", CoreUtils.getNouveauNomFichierFormate( serie ) );

            return getFileFromInputStream( in, serie );

        }
        else
        {
            LOG.debug( "Le fichier téléchargé n'est pas un fichier bittorent. ContentType=" + contentType );

            LOG.info( "Recherche du lien téléchargement" );
            String newHost = htmlSearchService.searchForDownloadLink( serieFileInformation.getUrl(), tracker, httpEntity );

            serieFileInformation.setUrl( newHost );
            if ( newHost != null )
            {
                LOG.info( "Nouveau lien de téléchargement : {} ", newHost );
                return getTorrentFile( serie, serieFileInformation, tracker );
            }
            else
            {
                LOG.error( "Lien de téléchargement introuvable" );
                throw new CookieException( "[FATAL] Le fichier téléchargé n'est pas un fichier Bittorent. ContentType=" + contentType );
            }
        }
    }

    private File getFileFromInputStream( InputStream in, Serie serie ) throws IOException
    {
        try (FileOutputStream writenFile = new FileOutputStream( CoreUtils.getNouveauNomFichierFormate( serie ) ))
        {
            byte[] buff = new byte[1024];
            int l = in.read( buff );
            while ( l > 0 )
            {
                writenFile.write( buff, 0, l );
                l = in.read( buff );
            }
        }
        catch ( IOException e )
        {
            throw e;
        }
        return new File( CoreUtils.getNouveauNomFichierFormate( serie ) );
    }

    private Tracker updateTrackerInformation( NomTracker nomTracker )
    {
        // Recherche du tracker en conf
        LOG.info( "Récupération de la conf du tracker" );
        Tracker trackerAUpdate = trackerConfigurationService.getTracker( nomTracker );
        LOG.info( "Sauvegarde des infos des cookies" );

        cookieSearchService.searchAndSaveCookie( trackerAUpdate );

        return trackerAUpdate;
    }

    private HttpEntity getHttpEntityFromUrl( String url, Tracker tracker ) throws SocketTimeoutException
    {
        LOG.info( "Recuperation du contenu de l'url " + url );
        HttpEntity content = null;
        try
        {
            RequestConfig requestConfig = RequestConfig.custom().setConnectionRequestTimeout( TIMEOUT_MS ).setConnectTimeout( TIMEOUT_MS ).setSocketTimeout( TIMEOUT_MS ).build();

            // Création du cookie store
            BasicCookieStore cookieStore = new BasicCookieStore();
            for ( Entry<String, String> entry : tracker.getHTTPHeaders().entrySet() )
            {
                LOG.info( "Ajout du cookie " + entry.getKey() + "=" + entry.getValue() );
                BasicClientCookie cookie = new BasicClientCookie( entry.getKey(), entry.getValue() );
                cookie.setVersion( 0 );
                cookie.setDomain( "." + tracker.getNomTracker().getCookieValue() );
                cookieStore.addCookie( cookie );
            }
            // Création du HttpClient
            HttpClient httpClient = HttpClientBuilder.create().setDefaultRequestConfig( requestConfig ).setDefaultCookieStore( cookieStore ).build();

            HttpGet get = new HttpGet( url );
            // Ajout d'un faux user agent pour tromper T411
            get.addHeader(HttpHeaders.USER_AGENT, "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.153 Safari/537.36");
            LOG.info( "Execution de la requête HTTP" );
            HttpResponse response = httpClient.execute(get  );
            LOG.info( "Code retour  : {}", response.getStatusLine().getStatusCode() );
            content = response.getEntity();
        }
        catch ( SocketTimeoutException e )
        {
            LOG.error( "Requête tombée en timeout", e );
            throw e;
        }
        catch ( Exception e )
        {
            LOG.error( "Impossible de récuperer le fichier torrent", e );
        }
        return content;
    }
}